<!--
TODO
error display timeout
escape character
-->
    
<%@ page import="java.beans.PropertyDescriptor"%>
<%@ page import="java.lang.annotation.Annotation" %>
<%@ page import="java.lang.reflect.Type" %>
<%@ page import="java.lang.reflect.Method" %>
<%@ page import="java.util.TreeSet" %>
<%@ page import="java.util.List" %>
<%@ page import="com.rhg.util.ObjectUtils" %>
<%@ page import="com.rhg.service.jsonrpc.impl.JsonRpcService" %>
<%@ page import="com.rhg.service.jsonrpc.impl.JsonRpcOperation" %>
<%@ page import="com.rhg.service.jsonrpc.description.Param" %>
<%@ page import="com.rhg.service.jsonrpc.description.Service" %>
<%@ page import="com.rhg.service.jsonrpc.description.Proc" %>
<%@ page import="com.rhg.util.ClassUtils" %>
<%@ page import="org.json.JSONObject"%>
<%@ page import="org.apache.commons.beanutils.PropertyUtils"%>

<%!

    private String getShortClassName(Class clazz) {
        if(clazz == null)
            return "";

        String className = clazz.getName();
        if(className.length() == 0)
            return "";
        char chars[] = className.toCharArray();
        int lastDot = 0;
        for(int i = 0; i < chars.length; i++)
            if(chars[i] == '.')
                lastDot = i + 1;
            else
            if(chars[i] == '$')
                chars[i] = '.';

        return new String(chars, lastDot, chars.length - lastDot);
    }        

    // renders the two table cells for input text box and parameter type
    public String render_param(String prefix, String name, Class clazz, Type genericType) {
        PropertyDescriptor[] pds = null;
        if (clazz != null) {
            pds = PropertyUtils.getPropertyDescriptors(clazz);
        }
        String myName = prefix + "|" + name;
        if (clazz == null || ObjectUtils.isClassSimpleType(clazz) || pds.length==0) {
            return "<td>" + getShortClassName(clazz) + "</td><td><input type=\"text\" id=\"" + myName + "\"/></td>";
        } else if (ObjectUtils.isClassArray(clazz)) {    
            Class elementType = null;
            if (genericType != null) {
                elementType = ClassUtils.getArrayElementType(genericType);
                if (elementType == null) {
                    elementType = clazz.getComponentType();
                }
            }

            String result = "<td>" + getShortClassName(clazz) + "</td><td><table id=\"" + myName + "\" border=1>";
            // show 2 elements initially for an array
            for( int i = 0; i < 2; i++ ){
                result += "<tr><td>" + i + "</td>";
                result += render_param(myName, Integer.toString(i), elementType, null);
                result += "</tr>";
            }
            result += "<tr><td colspan=\"3\"><input type=\"button\" value=\"add\" onclick=\"addArrayElement('" + myName + "')\"/></td></tr>";
            result += "</table></td>";
            return result;
        } else {
            String result = "<td>" + getShortClassName(clazz) + "</td><td><table id=\"" + myName + "\" border=1>";
            for( int i = 0; i < pds.length; i++ ){
                String key = pds[i].getName();
                if( "class".equals( key ) ){
                    // "class" is always a property. skip it. 
                    continue;
                }
                result += "<tr><td>" + key + "</td>";
                Type genericTypeForThisKey = null;
                if (pds[i].getWriteMethod() != null) {
                    genericTypeForThisKey = pds[i].getWriteMethod().getGenericParameterTypes()[0];
                } else if (pds[i].getReadMethod() != null) {
                    genericTypeForThisKey = pds[i].getReadMethod().getGenericParameterTypes()[0];
                }
                result += render_param(myName,  key, pds[i].getPropertyType(), genericTypeForThisKey);
                result += "</tr>";
            }
            result += "</table></td>";
            return result;

        }
    }
%>

<html>
<!-- assume all the javascripts are put at the same level as this jsp -->
<script src="../prototype.js" type="text/javascript"></script>
<script src="../JSON-js-prettyPrint.js" type="text/javascript"></script>
<script src="../expand-textarea.js" type="text/javascript"></script>
<script type="text/javascript" language="JavaScript">
function invoke(methodName) {
    Element.update(methodName + "_result", "running...");
    var allInputs = document.evaluate("//input", document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    var paramMap = [];
    for (var i = 0; i < allInputs.snapshotLength; i++) {
        var inputElement = allInputs.snapshotItem(i);
        var key = inputElement.getAttribute('id');
        if (key == null) {
            continue;
        }
        
        var prefix = (methodName + "_arg");
        if(key.indexOf(prefix) == 0) {
            key = key.substring(prefix.length+1);
            value = inputElement.value;
            // we don't check null here because server expects a null paremeter to be passed to it so that it can find the method
//             if (value == null || value == '') {
//                 continue;
//             }

            var currentParamMap = paramMap;
            var depth = 0;
            for (;;) {
                depth++;
                var index = key.indexOf('|');
                if (index < 0) {
                    if (depth == 1) {
                        key = key.substring("arg".length);
                    }                        
                    currentParamMap[key] = value;
                    break;
                }

                var parentKey = key.substring(0, index);
                if (depth == 1) {
                    parentKey = parentKey.substring("arg".length);
                }

                var nextIndex = key.substring(index+1).indexOf('|');
                var primitive = false;
                if (nextIndex == -1) {
                    primitive = true;
                    nextIndex = key.substring(index+1).length;
                }
                var nextKey = key.substring(index+1, nextIndex+index+1);
                if (nextIndex > 0 && !isNaN(parseInt(nextKey))) {
                    // is an array 
                    var position = parseInt(nextKey);

                    if (currentParamMap[parentKey] == null) {
                        currentParamMap[parentKey] = [];
                    }
                    if (primitive) {
                        currentParamMap[parentKey][position] = value;
                        break;
                    }
            
                    if (currentParamMap[parentKey][position] == null) {
                        currentParamMap[parentKey][position] = {};
                    }
                    currentParamMap = currentParamMap[parentKey][position];
                    key = key.substring(nextIndex+index+2);
                } else {
                    if (currentParamMap[parentKey] == null) {
                        currentParamMap[parentKey] = {};
                    }
                    currentParamMap = currentParamMap[parentKey];
                    key = key.substring(index+1);
                }
            }
        }
    }
    var jsonRequest = "{\"id\": null, \"method\": \"" + methodName + "\", \"params\" : " + objToJson(paramMap) + "}";
    document.getElementById(methodName + "_json_request").value = stringToPrettyJson(jsonRequest);
    send(methodName, jsonRequest);
}

function post(methodName) {
    Element.update(methodName + "_result", "running...");
    send(methodName, $F(methodName+'_json_request'));
}

function send(methodName, jsonRequest) {
    url = methodName;
    var ajax = new Ajax.Request(
        url,
        {method: 'post', parameters: jsonRequest, onSuccess: function(req) {renderResult(req, methodName);}, onFailure: reportError});
}

function stringToPrettyJson(str) {
    var result = window.eval( "(" + str + ")" );    
    return result.toJSONString(true);
}

function renderResult(req, methodName) {
    var response = req.responseText;
    result = response + "<pre>" + stringToPrettyJson(response) + "</pre>";
    Element.update(methodName + "_result", result);
}

function reportError(request) {
    alert("error code " + request.status);
}

function objToJson(obj) {
    var result = "{";
    if (isArray(obj)) {
        result = "[";
    }
    
    var first = true;
    for (var key in obj) {
        if (obj[key] != null) {
            if (typeof(obj[key]) == 'function') {
                continue;
            }
            
            if (first) {
                first = false;
            } else {
                result += ",";
            }
            if (!isArray(obj)) {
                result += key;
                result += ":";
            }
            if (isArray(obj[key])) {
                result += "[";
                var lastNonNull = -1;
                for (var i = 0; i < obj[key].length; i++) {                
                    if (!isEmpty(obj[key][i])) {
                        lastNonNull = i;
                    }
                }
                var firstArrayElement = true;
                for (var i = 0; i <= lastNonNull; i++) {
                    if (firstArrayElement) {
                        firstArrayElement = false;
                    } else {
                        result += ",";
                    }
                    if (typeof(obj[key][i]) == "object") {
                        result += objToJson(obj[key][i]);
                    } else {
                        result += quote(obj[key][i]);
                    }
                }
                result += "]";
            } else if (typeof(obj[key]) == "object") {
                result += objToJson(obj[key]);
            } else {
                result += quote(obj[key]);
            }
        }
    }
    if (isArray(obj)) {
        result += "]";
    } else {
        result += "}";
    }
    return result;
}

function quote(v) {
    if (v == null || v == '') { // empty string is treated as null
        return "null";
    } else if (isNaN(parseInt(v))){
        return '"' + v + '"';
    } else {
        return v;
    }
}

function addArrayElement(parentId) {
    var parent = document.getElementById(parentId);
    var newNode = parent.firstChild.firstChild.cloneNode(true);
    var count = parent.firstChild.childNodes.length - 1; // minus the extra "add" button
    newNode.firstChild.innerHTML = count;

    var html = newNode.childNodes.item(2).innerHTML;
    var oldId = parentId + "|" + "0";
    var newId = parentId + "|" + count;

    oldId = oldId.replace(/\|/g, '\\|'); // escape | in regexp
    var re = new RegExp(oldId, "g");
    html = html.replace(re, newId);

    newNode.childNodes.item(2).innerHTML = html;
    parent.firstChild.insertBefore(newNode, parent.firstChild.lastChild);
}

function isArray(v) {
    return v && typeof v === 'object' && typeof v.length === 'number' &&
        !(v.propertyIsEnumerable('length'));
}

function isEmpty(o) {
    if (o == null) {
        return true;
    }

    if (typeof(o) == 'string') {
        return (o == '');
    }
        
    if (isArray(o)) {
        return o.length == 0;
    }
    if (typeof(o) == 'object') {
        for (var i in o) {
            if (typeof(o[i]) != 'function' && o[i] != null && o[i] != '') { 
                return false;
            }
        }
    }
    return true;
}

</script>

<body onload="setup_expand_area()">
<%
    JsonRpcService service = (JsonRpcService)request.getAttribute("model");
    Annotation[] classAnnotations = service.getServiceClass().getAnnotations();
    Service serviceAnnontation = null;
    if (classAnnotations.length >= 1 && classAnnotations[0] instanceof Service) {
        serviceAnnontation = (Service) classAnnotations[0];
    }
    String serviceName = service.getServiceBean().getClass().getName();
    if (serviceAnnontation != null) {
        serviceName = serviceAnnontation.name();
    }
%>
<title><%=serviceName%> Inspector</title>
<h1>JSON-RPC Service Inspector</h1>
<table border="0">
<tr>
    <td>name</td>           
    <td><%=serviceName%></td>
<%
    if (serviceAnnontation != null) {
%>
</tr>
<tr>
    <td>id</td>           
    <td><%=serviceAnnontation.id()%></td>
</tr>
<tr>
    <td>address</td>           
    <td><%=serviceAnnontation.address()%></td>
</tr>
<tr>
    <td>summary</td>           
    <td><%=serviceAnnontation.summary()%></td>
</tr>
<tr>
    <td>help</td>           
    <td><%=serviceAnnontation.help()%></td>
</tr>
<tr>
    <td>version</td>           
    <td><%=serviceAnnontation.version()%></td>
</tr>
<tr>
    <td>sdversion</td>           
    <td><%=serviceAnnontation.sdversion()%></td>
<%
    } 
%>

</tr>
</table>        

    <h2>Available methods </h2>
<ul>

<%
    TreeSet<String> sortedSet = new TreeSet<String>(service.getOperations().keySet());
    for (String operationName : sortedSet) {
        String methodName = operationName;
%>

<li><a href="#<%=methodName%>"><%=methodName%></a></li>
     
<%     
    }
%>
</ul>

<form id="methodForm">


<%
    for (String operationName : sortedSet) {
        List<JsonRpcOperation> operationList = service.getOperations().get(operationName);
        if (operationList.size() > 0) {
            JsonRpcOperation operation = operationList.get(0);
            Method method = operation.getMethod();
            String methodName = method.getName();
                
%>

<h3 id="<%=methodName%>"><%=methodName%>
<input type="button" id="toggle_<%=methodName%>" value="hide" name="toggle" onclick="Element.toggle('<%=methodName%>_details'); var toggle = document.getElementById('toggle_<%=methodName%>'); if (toggle.value=='show') toggle.value='hide'; else toggle.value='show'; "/>
</h3>
<div id="<%=methodName%>_details">

<%
            Annotation[] procAnnotations = operation.getMethod().getAnnotations();
			if (procAnnotations.length >= 1) {
                String methodSummary = "";
                String methodHelp = "";
                String methodIdempotent = "false";
			    Proc procAnnotation = (Proc) procAnnotations[0];
                methodSummary = procAnnotation.summary();
                methodHelp = procAnnotation.help();
                methodIdempotent = Boolean.toString(procAnnotation.idempotent());

%>     
<table border="0">
<tr>
     <td>summary</td>
     <td><%=methodSummary%></td>
</tr>
<tr>
     <td>help</td>
     <td><%=methodHelp%></td>
</tr>
<tr>
     <td>idempotent</td>
     <td><%=methodIdempotent%></td>
</tr>
</table>

<%
            }
%>

<dl>
<dt>
</dt>

<dd>
<table border=1>
<%

            Type[] genericTypes = method.getGenericParameterTypes();
            int index = 0;
            for (Class paramClass : method.getParameterTypes()) {
                Annotation[][] paramsAnnotations = method.getParameterAnnotations();
                String defaultParamName = "arg" + index;
                String paramName = defaultParamName;
                // find the annotated name if it exists (there is one annotated param name, usually)
                for (Annotation annotation : paramsAnnotations[index]) {
                    Param paramAnnotation = (Param) annotation;
                    paramName = paramAnnotation.name();
                }
                String paramForm = render_param(methodName + "_arg", defaultParamName, paramClass, genericTypes[index]);
%>

<tr>
<td><%=paramName%></td>
<%=paramForm%>
</tr>

<%
                index++;
            }
%>
</table>
</dd>

<dd>
<input id="invoke_<%=methodName%>" name="invoke_<%=methodName%>" value="invoke" type="button" onclick="invoke('<%=methodName%>')"/>
</dd>

<dd>
raw json request
</dd>

<dd>
      <textarea id="<%=methodName%>_json_request" name="<%=methodName%>_json_request" cols="100" rows="10"></textarea>
</dd>

<dd>
      <input id="post_<%=methodName%>" name="post_<%=methodName%>" value="post" type="button" onclick="post('<%=methodName%>')"/>
</dd>

<dd>
result:(<%=method.getGenericReturnType()%>)
</dd>

<dd>
<fieldset>
<div id="<%=methodName%>_result"></div>
</fieldset>
</dd>

</dl>
</div>
<%
        }
    }
%>


</form>

</body>
</html>
